#include "linux/delay.h"
#include "asm/dma.h"
#include "asm/io.h"

#include "los_typedef.h"
#include "los_task.h"
#include "los_base.h"
#include "los_event.h"
#include "errno.h"

#include "linux/interrupt.h"
#include "linux/kernel.h"
#include "linux/spinlock.h"
#include "uart_dw.h"
#include "asm/platform.h"
#include <sys/bus.h>

#include "uart_dev.h"
#include "string.h"

#include "los_magickey.h"
	
#define UART_IIR	0x08
#define UART_LSR	0x14
#define UART_USR	0x7c
#define UART_LCR	0x0c
#define UART_RBR	0x00
#define UART_MCR	0x10
#define UART_IER	0x04

#define UART_LSR_DR	            BIT(0)
#define UART_LSR_OE	            BIT(1)
#define UART_LSR_PE	            BIT(2)
#define UART_LSR_FE	            BIT(3)
#define UART_LSR_BI           	BIT(4)
#define UART_LSR_THRE	        BIT(5)
#define UART_LSR_TEMT	        BIT(6)
#define UART_LSR_RXFIFOE	    BIT(7)
#define UART_LSR_BRK_ERROR_BITS	0x1e

LITE_OS_SEC_BSS STATIC SPIN_LOCK_INIT(g_uartOutputSpin);

extern char uart_putc (char c);

STATIC INLINE UINTPTR uart_to_ptr(UINTPTR n)
{
    (VOID)n;
    return UART_REG_BASE;
}

STATIC VOID UartPutStr(UINTPTR base, const CHAR *s, UINT32 len)
{
    UINT32 i;

    for (i = 0; i < len; i++) {
        if (*(s + i) == '\n') {
			uart_putc(*"\r");
        }
		uart_putc(*(s + i));
    }
}

STATIC UINT32 UartPutsReg(UINTPTR base, const CHAR *s, UINT32 len, BOOL isLock)
{
    UINT32 intSave;

    if (isLock) {
        LOS_SpinLockSave(&g_uartOutputSpin, &intSave);
        UartPutStr(base, s, len);
        LOS_SpinUnlockRestore(&g_uartOutputSpin, intSave);
    } else {
        UartPutStr(base, s, len);
    }

    return len;
}

STATIC VOID UartPuts(const CHAR *s, UINT32 len, BOOL isLock)
{
    UINTPTR base = uart_to_ptr(0);
    UartPutsReg(base, s, len, isLock);
}


struct dw_port {
	int enable;
	unsigned long phys_base;
	unsigned int irq_num;
	struct uart_driver_data *udd;
};

#define FIFO_SIZE    128

static irqreturn_t dw_irq(int irq, void *data)
{
	char buf[FIFO_SIZE];
	unsigned int count = 0;
	struct dw_port *port = NULL;
	struct uart_driver_data *udd = (struct uart_driver_data *)data;
	unsigned int iir, lsr;
	int max_count = 256;
	unsigned char ch = 0;
	
	if (udd == NULL) {
		uart_error("udd is null!\n");
		return IRQ_HANDLED;
	}
	port = (struct dw_port *)udd->private;
	
	iir = readl(port->phys_base + UART_IIR);
	lsr = readl(port->phys_base + UART_LSR);
	
	if(iir == BIT(7)){
		readl(port->phys_base + UART_USR);
	} else {
		if(lsr & (UART_LSR_DR | UART_LSR_BI)){
			do{
				if(lsr & UART_LSR_DR){
					ch = readb(port->phys_base + UART_RBR);
				}
				
				if(lsr & 0x1){
					buf[count++] = (char)ch;
				}
				lsr = readl(port->phys_base + UART_LSR);
			}while(lsr & (UART_LSR_DR | UART_LSR_BI) && (max_count-- > 0));
			udd->recv(udd, buf, count);
		}
	}
	return IRQ_HANDLED;
}


static int dw_config_in(struct uart_driver_data *udd)
{
	return 0;
}

static int dw_startup(struct uart_driver_data *udd) 
{
	int ret = 0;
	struct dw_port *port = NULL;

	if (udd == NULL) {
		uart_error("udd is null!\n");
		return -EFAULT;
	}

	port = (struct dw_port *)udd->private;
	if (!port) {
		uart_error("port is null!");
		return -EFAULT;
	}
	/* enable the clock */
	LOS_TaskLock();
	//uart_clk_cfg(udd->num, true); //use for hi3518
	LOS_TaskUnlock();
	
	
	writel(BIT(0) | BIT(2), port->phys_base + UART_IER);
	
	ret = request_irq(port->irq_num, (irq_handler_t)dw_irq,
							  0, "uart_dw", udd);

	dw_config_in(udd);

	return ret;
}

static int dw_shutdown(struct uart_driver_data *udd)
{
	return 0;
}

static int dw_start_tx(struct uart_driver_data *udd, const char *buf, size_t count)
{
	unsigned int tx_len = count;
	struct dw_port *port = NULL;
	char value;
	unsigned int i;
	int ret = 0;

	if (udd == NULL) {
		uart_error("udd is null!\n");
		return -EFAULT;
	}
	port = (struct dw_port *)udd->private;
	if (!port) {
		uart_error("port is null!");
		return -EFAULT;
	}
	/* UART_WITH_LOCK: there is a spinlock in the function to write reg in order. */
	for (i = 0; i < tx_len; i++ ){
		ret = LOS_CopyToKernel((void *)&value, sizeof(char),(void *)(buf++), sizeof(char));
		if (ret) {
			return i;
		}
		(void)UartPutsReg(port->phys_base, &value, 1, UART_WITH_LOCK);
	}
	return count;
}

static int dw_config(struct uart_driver_data *udd)
{
	return dw_config_in(udd);
}

static struct uart_ops dw_uops = {
	.startup        = dw_startup,
	.shutdown       = dw_shutdown,
	.start_tx       = dw_start_tx,
	.config         = dw_config,
};

#define MAX_DEV_NAME_SIZE  32
extern const struct file_operations_vfs uartdev_fops;
extern struct uart_driver_data *get_udd_by_unit(int unit);

static int dw_attach(device_t self)
{
	struct resource *res = NULL;
	char dev_name[MAX_DEV_NAME_SIZE];
	struct dw_port *port = NULL;
	int unit = device_get_unit(self);
	struct uart_softc *sc = device_get_softc(self);
	struct uart_driver_data *udd = sc->udd;

	if (udd == NULL) {
		uart_error("dw_attach get uart driver data err!");
		return -1;
	}
	port = (struct dw_port *)LOS_MemAlloc(m_aucSysMem0, sizeof(struct dw_port));
	if (!port) {
		return -1;
	}
	memset_s(port, sizeof(struct dw_port), 0, sizeof(struct dw_port));
	res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &unit, 0);
	if (!res) {
		goto err;
	}
	port->phys_base = (unsigned long)(uintptr_t)ioremap(res->start, res->count);
	if (!port->phys_base) {
		goto err;
	}
	res = bus_alloc_resource_any(self, SYS_RES_IRQ, &unit, 0);
	if (!res) {
		goto err;
	}
	
	port->irq_num = res->start;
	if (port->irq_num == LOS_NOK) {
		goto err;
	}

	udd->private = port;
	udd->ops = &dw_uops;
	port->udd = udd;
	udd->recv = uart_recv_notify;
	udd->count = 0;
	memset_s(dev_name, MAX_DEV_NAME_SIZE, 0, MAX_DEV_NAME_SIZE);
	snprintf_s(dev_name, MAX_DEV_NAME_SIZE, MAX_DEV_NAME_SIZE - 1, "/dev/uartdev-%d", udd->num);
	if (register_driver(dev_name, &uartdev_fops, 0666, udd)) {
		uart_error("gen /dev/uartdev-%d fail!\n", udd->num);
		goto err;
	}

	return 0;
err:
	iounmap((void *)(uintptr_t)port->phys_base);
	(VOID)LOS_MemFree(m_aucSysMem0, port);
	port =  NULL;
	return -1;
}

static int dw_probe(device_t self)
{
	return (BUS_PROBE_DEFAULT);
}

static int dw_detach(device_t self)
{
	struct uart_softc *sc = device_get_softc(self);
	struct uart_driver_data *udd = sc->udd;
	struct dw_port *port = NULL;
	char dev_name[MAX_DEV_NAME_SIZE];
	if (udd == NULL || (udd->state != UART_STATE_USEABLE)) {
		uart_error("dw_detach uart driver data state invalid!");
		return -1;
	}

	(void)memset_s(dev_name, MAX_DEV_NAME_SIZE, 0, MAX_DEV_NAME_SIZE);
	(void)snprintf_s(dev_name, MAX_DEV_NAME_SIZE, MAX_DEV_NAME_SIZE - 1, "/dev/uartdev-%d", udd->num);
	if (unregister_driver(dev_name)) {
		uart_error("dw_detach unregister /dev/uartdev-%d fail!\n", udd->num);
	}
	port = udd->private;
	if (port == NULL) {
		return -1;
	}
	if (port->phys_base) {
		iounmap((void *)(uintptr_t)port->phys_base);
		port->phys_base = 0;
	}
	(VOID)LOS_MemFree(m_aucSysMem0, port);
	udd->private = NULL;
	return 0;
}

static device_method_t uart_methods[] = 
{
	/* Device interface */
	DEVMETHOD(device_probe, dw_probe),
	DEVMETHOD(device_attach, dw_attach),
	DEVMETHOD(device_detach, dw_detach),
	DEVMETHOD(device_shutdown, bus_generic_shutdown),
	DEVMETHOD_END
};

static driver_t uart_driver = 
{
	.name = "uart",
	.methods = uart_methods,
	.size = sizeof(struct uart_softc),
};

static devclass_t uart_devclass;
DRIVER_MODULE(uart, nexus, uart_driver, uart_devclass, 0, 0);

int uart_dev_init(void)
{
	return driver_module_handler(NULL, MOD_LOAD, &uart_nexus_driver_mod);
}

void uart_dev_exit(void)
{
	driver_module_handler(NULL, MOD_UNLOAD, &uart_nexus_driver_mod);
}
